package com.opencee.cloud.bpm.controller;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.opencee.cloud.bpm.entity.BpmCategoryEntity;
import com.opencee.cloud.bpm.mapper.BpmProcessExtendsMapper;
import com.opencee.cloud.bpm.ro.BpmModelParams;
import com.opencee.cloud.bpm.service.IBpmCategoryService;
import com.opencee.cloud.bpm.vo.BpmModelVO;
import com.opencee.common.exception.BaseFailException;
import com.opencee.common.model.ApiResult;
import com.opencee.common.model.PageResult;
import com.opencee.common.utils.WebUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author yadu
 */
@Slf4j
@Api(value = "模型管理", tags = "模型管理")
@RestController
@RequestMapping(value = "/model")
public class BpmModelController {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private BpmProcessExtendsMapper bpmProcessExtendsMapper;
    @Autowired
    private IBpmCategoryService categoryService;

    @ApiOperation(value = "查询模型列表", notes = "查询模型列表")
    @GetMapping(value = "/page")
    public ApiResult<PageResult> page(BpmModelParams params) {
        ModelQuery query = repositoryService.createModelQuery();
        if (!StringUtils.isEmpty(params.getKey())) {
            query.modelKey(params.getKey());
        }
        if (!StringUtils.isEmpty(params.getCategory())) {
            query.modelCategory(params.getCategory());
        }
        if (!StringUtils.isEmpty(params.getName())) {
            query.modelNameLike(params.getName());
        }
        Long count = query.count();
        List<Model> list = query.orderByModelId().desc().listPage(params.getStart(), params.getLimit());
        Set<Long> categoryIds = list.stream().filter(t -> !StringUtils.isEmpty(t.getCategory())).map(t -> Long.parseLong(t.getCategory())).collect(Collectors.toSet());
        Map<Long, BpmCategoryEntity> categoryMap = categoryService.mapByIds(categoryIds);
        List<BpmModelVO> records = list.stream().map(t -> {
            BpmModelVO vo = new BpmModelVO();
            BeanUtils.copyProperties(t, vo);
            if (!StringUtils.isEmpty(t.getCategory())) {
                Long cid = Long.parseLong(t.getCategory());
                BpmCategoryEntity c = categoryMap.get(cid);
                if (c != null) {
                    vo.setCategoryName(c.getName());
                }
            }
            return vo;
        }).collect(Collectors.toList());
        PageResult page = new PageResult();
        page.setRecords(records);
        page.setTotal(count.intValue());
        return ApiResult.ok().data(page);
    }


    /**
     * 获取模型xml
     */
    @ApiOperation(value = "获取模型xml", notes = "获取模型xml")
    @GetMapping("/info")
    public ApiResult getById(
            @RequestParam(name = "id") String id) throws IOException {
        ObjectNode modelJson = null;
        Model model = repositoryService.getModel(id);
        if (model != null) {
            model = repositoryService.getModel(id);
            modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            byte[] editorSourceExtra = repositoryService.getModelEditorSourceExtra(id);
            if (editorSourceExtra != null) {
                ObjectNode extraJson = (ObjectNode) new ObjectMapper().readTree(editorSourceExtra);
                modelJson.put("extra", extraJson);
            }
        }
        return ApiResult.ok().data(modelJson);
    }

    /**
     * 创建模型
     */
    @ApiOperation(value = "创建模型", notes = "创建模型")
    @PostMapping("/save")
    public ApiResult save(
            @RequestParam(name = "id", required = false) String id,
            @RequestParam("name") String name,
            @RequestParam(value = "category", defaultValue = "1") String category,
            @RequestParam("description") String description) throws UnsupportedEncodingException, JsonProcessingException {
        Model model = null;
        if (StringUtils.isNotEmpty(id)) {
            // 修改
            model = repositoryService.getModel(id);
            ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
            modelJson.put("name", name);
            modelJson.put("description", description);
            model.setMetaInfo(modelJson.toString());
            model.setName(name);
            model.setCategory(category);
            repositoryService.saveModel(model);
            byte[] extraBytes = repositoryService.getModelEditorSourceExtra(id);
            try {
                if (extraBytes != null && extraBytes.length > 0) {
                    ObjectNode extraJson = objectMapper.readValue(extraBytes, ObjectNode.class);
                    String xml = extraJson.get("xml").asText();
                    XMLInputFactory xif = XMLInputFactory.newInstance();
                    InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(xml.getBytes("utf-8")), "UTF-8");
                    XMLStreamReader xtr = xif.createXMLStreamReader(in);
                    BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
                    BpmnModel bpmnModel = bpmnXMLConverter.convertToBpmnModel(xtr);
                    Process process = bpmnModel.getMainProcess();
                    if (process != null) {
                        process.setName(name);
                        byte[] bytes = bpmnXMLConverter.convertToXML(bpmnModel, "utf-8");
                        xml = new String(bytes, "utf-8");
                        extraJson.put("xml", xml.replace("\n", ""));
                        repositoryService.addModelEditorSourceExtra(model.getId(), objectMapper.writeValueAsBytes(extraJson));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            String key = "PROC_" + IdWorker.get32UUID();
            // 新增
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "canvas");
            editorNode.put("resourceId", "canvas");
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
            editorNode.put("stencilset", stencilSetNode);
            model = repositoryService.newModel();
            ObjectNode modelJson = objectMapper.createObjectNode();
            modelJson.put("id", key);
            modelJson.put("name", name);
            modelJson.put("revision", 1);
            description = StringUtils.defaultString(description);
            modelJson.put("description", description);
            model.setMetaInfo(modelJson.toString());
            model.setName(name);
            model.setCategory(category);
            model.setKey(StringUtils.defaultString(key));
            repositoryService.saveModel(model);
            repositoryService.addModelEditorSource(model.getId(), editorNode.toString().getBytes("utf-8"));
        }
        return ApiResult.ok().data(model.getId());
    }


    @ApiOperation(value = "保存模型设计", notes = "保存模型设计")
    @PostMapping(value = "/editor/save")
    public ApiResult saveModelEditor(@RequestParam(name = "id") String id,
                                     @RequestParam("json") String json,
                                     @RequestParam("svg") String svg,
                                     @RequestParam("xml") String xml) {
        try {
            Model model = repositoryService.getModel(id);
            repositoryService.addModelEditorSource(model.getId(), json.getBytes("utf-8"));
            ObjectNode extraJson = objectMapper.createObjectNode();
            extraJson.put("xml", xml);
            // 添加附加项 xml和svg
            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(xml.getBytes("utf-8")), "UTF-8");
            XMLStreamReader xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            extraJson.put("svg", svg);
            String name = bpmnModel.getMainProcess().getName();
            if (!model.getName().equals(name)) {
                ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                modelJson.put("name", name);
                model.setMetaInfo(modelJson.toString());
                model.setName(name);
                repositoryService.saveModel(model);
            }
            repositoryService.addModelEditorSourceExtra(model.getId(), extraJson.toString().getBytes("utf-8"));
        } catch (Exception e) {
            log.error("Error saving model", e);
            throw new ActivitiException("Error saving model", e);
        }
        return ApiResult.ok();
    }

    /**
     * 部署模型
     */
    @ApiOperation(value = "部署模型", notes = "部署模型")
    @PostMapping(value = "deploy")
    public ApiResult deploy(@RequestParam("id") String id, @RequestParam(value = "categoryId", defaultValue = "1") String categoryId) throws IOException, TranscoderException, XMLStreamException {
        Model modelData = repositoryService.getModel(id);
        byte[] editorSourceExtra = repositoryService.getModelEditorSourceExtra(modelData.getId());
        if (editorSourceExtra == null) {
            return ApiResult.failed().msg("模型数据为空，请先设计流程并成功保存，再进行发布。");
        }
        ObjectNode extraJson = (ObjectNode) new ObjectMapper().readTree(editorSourceExtra);
        String xml = extraJson.get("xml").asText();
        String svg = extraJson.get("svg").asText();
        final byte[] xmlBytes = xml.getBytes("utf-8");

        if (xmlBytes == null) {
            return ApiResult.failed().msg("模型数据为空，请先设计流程并成功保存，再进行发布。");
        }
        // 添加附加项 xml和svg
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(xmlBytes), "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
        if (bpmnModel.getMainProcess() == null) {
            return ApiResult.failed().msg("数据模型不符要求，请至少设计一条主线流程。");
        }

        String xmlName = modelData.getKey() + ".bpmn20.xml";
        String pngName = modelData.getKey() + ".png";
        DeploymentBuilder builder = repositoryService.createDeployment()
                .enableDuplicateFiltering()
                .key(modelData.getKey())
                .category(modelData.getCategory())
                .tenantId(modelData.getTenantId())
                .name(modelData.getName())
                .addString(xmlName, new String(xmlBytes));
        try {
            if (StringUtils.isNotEmpty(svg)) {
                InputStream svgStream = new ByteArrayInputStream(svg.getBytes("utf-8"));
                TranscoderInput input = new TranscoderInput(svgStream);
                PNGTranscoder transcoder = new PNGTranscoder();
                // Setup output
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                TranscoderOutput output = new TranscoderOutput(outStream);
                // Do the transformation
                transcoder.transcode(input, output);
                final byte[] svgBytes = outStream.toByteArray();
                // 发布流程图片
                builder.addBytes(pngName, svgBytes);
            }
        } catch (Exception e) {
            throw new BaseFailException("部署失败,流程图片生成发生错误");
        }
        Deployment deployment = builder.deploy();
        modelData.setDeploymentId(deployment.getId());
        bpmProcessExtendsMapper.updateProcessDefinitionCategory(deployment.getId(), categoryId);
        repositoryService.saveModel(modelData);
        return ApiResult.ok().msg("部署成功，部署ID=" + deployment.getId());
    }

    /**
     * 导出model对象为指定类型
     *
     * @param id   模型ID
     * @param type 导出文件类型(bpmn\json)
     */
    @ApiOperation(value = "导出model对象为指定类型", notes = "导出model对象为指定类型")
    @GetMapping(value = "download/{id}/{type}")
    public ApiResult downloadFile(@PathVariable("id") String id,
                                  @PathVariable("type") String type,
                                  HttpServletResponse response) throws IOException, TranscoderException {
        Model modelData = repositoryService.getModel(id);
        byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
        byte[] editorSourceExtra = repositoryService.getModelEditorSourceExtra(modelData.getId());
        if (editorSourceExtra == null) {
            return ApiResult.failed().msg("模型数据为空，请先设计流程并成功保存，再进行发布。");
        }
        ObjectNode extraJson = (ObjectNode) new ObjectMapper().readTree(editorSourceExtra);

        String xml = extraJson.get("xml").asText();
        String svg = extraJson.get("svg").asText();


        String filename = "";
        byte[] exportBytes = null;


        if ("bpmn".equals(type)) {
            exportBytes = xml.getBytes("utf-8");
            filename = modelData.getName() + ".bpmn20.xml";
        } else if ("json".equals(type)) {
            exportBytes = modelEditorSource;
            filename = modelData.getName() + ".json";
        } else if ("png".equals(type)) {
            InputStream svgStream = new ByteArrayInputStream(svg.getBytes("utf-8"));
            TranscoderInput input = new TranscoderInput(svgStream);

            PNGTranscoder transcoder = new PNGTranscoder();
            // Setup output
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            TranscoderOutput output = new TranscoderOutput(outStream);

            // Do the transformation
            transcoder.transcode(input, output);
            exportBytes = outStream.toByteArray();
            filename = modelData.getName() + ".png";
        }
        WebUtil.setFileDownloadHeader(response, filename);
        ByteArrayInputStream in = new ByteArrayInputStream(exportBytes);
        OutputStream os = response.getOutputStream();
        IOUtils.copy(in, os);
        IOUtils.closeQuietly(in);
        IOUtils.closeQuietly(os);
        return ApiResult.ok();
    }

    @ApiOperation(value = "删除模型", notes = "删除模型")
    @PostMapping(value = "remove")
    public ApiResult remove(@RequestParam("id") String id) {
        repositoryService.deleteModel(id);
        return ApiResult.ok();
    }


}
